package com.mz.nhoz.xlsx;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.mz.nhoz.util.ValueComparator;
import com.mz.nhoz.xlsx.exception.NoXlsxHeadersException;
import com.mz.nhoz.xlsx.exception.NoXlsxRecordsException;
import com.mz.nhoz.xlsx.exception.XlsxWrongTypeException;

/**
 * Manejador sencillo de tablas de excel. <br/>
 * <br/>
 * Permite leer facilmente tablas unicas de pagina de excel que inicien en celda
 * 1A. La tabla debe poseer como primera fila los encabezados de la misma (ej:
 * Nombre , Edad , Salario, Dni , etc.), las demas filas deben ser registros de
 * datos. No debe poseer encabezados de columna. Los registros de la tabla deben
 * organizarse de forma vertical tal que cada registro es una fila. No puede
 * haber encabezados (columnas en fila 1) con mismo nombre. La tabla debe
 * contener al menos un registro para ser leidas con esta clase.
 * 
 * @author Martin
 *
 */
public class XlsxSimpleVertTableReader implements XlsxTableReader {
	protected FileInputStream fileInputStream;
	XSSFWorkbook workbook;
	protected XSSFSheet sheet;
	protected XlsxTableHeaderMap tableHeaderMap = new XlsxTableHeaderMap();

	protected int columnCount = 0;
	protected int rowCount = 0;

	XSSFWorkbook getWorkbook() {
		return workbook;
	}

	/**
	 * Crea una nueva instancia de manejador de tablas.
	 * 
	 * @param xlsxFile
	 *            - Archivo xlsx a manipular.
	 * @param sheetIndex
	 *            - Indice de hoja donde se encuentra la tabla dentro del
	 *            archivo.
	 * @throws Exception
	 *             - Error ante inexistencia de archivo o error de formato del
	 *             mismo.
	 */
	public XlsxSimpleVertTableReader(File xlsxFile, int sheetIndex) throws Exception {
		super();

		initWorkbook(xlsxFile, sheetIndex);

		readCellTypes();
	}// constructor

	private void readCellTypes() throws Exception {
		Iterator<Row> rowIterator = getRowIterator();
		Row headerRow;
		try {
			headerRow = rowIterator.next();
		} catch (Exception e) {
			throw new NoXlsxHeadersException("No hay encabezados de tabla!", e);
		}
		// For each row, iterate through all the columns

		Row firstRecordRow;
		try {
			firstRecordRow = rowIterator.next();
		} catch (Exception e) {
			throw new NoXlsxRecordsException("No hay registros en la tabla!", e);
		}

		Iterator<Cell> headerRecordIterator = headerRow.cellIterator();
		Iterator<Cell> firstRecordIterator = firstRecordRow.cellIterator();

		int columnIndex = 0;
		while (headerRecordIterator.hasNext()) {
			Cell headerCell = headerRecordIterator.next();
			int headerCellType = headerCell.getCellType();

			if (headerCellType != XlsxTableHeader.TYPE_STRING) {
				throw new XlsxWrongTypeException(
						"Imposible leer nombre de encabezado de tabla dado que el mismo no es de tipo String.");
			}

			String headerCellName = headerCell.getStringCellValue();
			Cell firstRecordCell = firstRecordIterator.next();
			int firstRecordCellType = firstRecordCell.getCellType();

			XlsxTableHeader tableHeader = new XlsxTableHeader(firstRecordCellType, headerCellName);
			addTableHeaderToMap(tableHeader.getName(), tableHeader);

			columnIndex++;
		}
	}// readCellTypes

	void addTableHeaderToMap(String headerName, XlsxTableHeader header) {
		this.tableHeaderMap.put(headerName, header);
	}

	void initWorkbook(File xlsxFile, int sheetIndex) throws FileNotFoundException, IOException {
		this.fileInputStream = new FileInputStream(xlsxFile);
		// Create Workbook instance holding reference to .xlsx file
		this.workbook = new XSSFWorkbook(fileInputStream);
		// Get first/desired sheet from the workbook
		this.sheet = this.workbook.getSheetAt(sheetIndex);
	}// initWorkbook

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mz.nhoz.xlsx.XlsxTableReader#getColumnCount()
	 */
	@Override
	public int getColumnCount() {
		if (this.columnCount > 0) {
			return this.columnCount;
		}

		setColumnCount();

		return this.columnCount;
	}// getColumnCount

	protected void setColumnCount() {
		Iterator<Row> rowIterator = sheet.iterator();
		Row headerRow = rowIterator.next();
		Iterator<Cell> headerCellIterator = headerRow.cellIterator();

		while (headerCellIterator.hasNext()) {
			this.columnCount++;
			headerCellIterator.next();
		}
	}// setColumnCount

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mz.nhoz.xlsx.XlsxTableReader#getRowCount()
	 */
	@Override
	public int getRowCount() {
		if (this.rowCount > 0) {
			return this.rowCount;
		}

		setRowCount();

		return this.rowCount;
	}// getRowCount

	protected void setRowCount() {
		Iterator<Row> rowIterator = sheet.iterator();

		while (rowIterator.hasNext()) {
			this.rowCount++;
			rowIterator.next();
		}
	}// setRowCount

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mz.nhoz.xlsx.XlsxTableReader#getRecordCount()
	 */
	@Override
	public int getRecordCount() {
		int recordCount = getRowCount() - 1;
		return recordCount;
	}// getRecordCount

	@Override
	protected void finalize() throws Throwable {
		try {
			fileInputStream.close();
		} catch (Exception e) {
		}
	}// finalize

	private Row getRow(int rowIndex) {
		if (rowIndex >= getRowCount()) {
			return null;
		}
		Iterator<Row> rowIterator = getRowIterator();

		Row row = rowIterator.next();
		if (rowIndex == 0) {
			return row;
		}

		for (int i = 0; i < rowIndex; i++) {
			row = rowIterator.next();
		}

		return row;
	}// getRow

	/**
	 * Retorna una celda de la tabla.
	 * 
	 * @param rowIndex
	 *            - indice de fila iniciando en 0.
	 * @param colIndex
	 *            - indice de columna iniciando en 0.
	 * @return celda de la tabla, null en caso que la celda buscada no exista.
	 */
	Cell getCell(int rowIndex, int colIndex) {
		Row row = getRow(rowIndex);
		if (row == null || colIndex >= getColumnCount()) {
			return null;
		}

		Iterator<Cell> cellIterator = row.cellIterator();
		Cell cell = cellIterator.next();
		if (colIndex == 0) {
			return cell;
		}

		for (int i = 0; i < colIndex; i++) {
			cell = cellIterator.next();
		}

		return cell;
	}// getCell

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mz.nhoz.xlsx.XlsxTableReader#getCellValue(int, int)
	 */
	@Override
	public Object getCellValue(int rowIndex, int colIndex) {
		Cell cell = getCell(rowIndex, colIndex);
		if (cell == null) {
			return null;
		}

		return new CellValueInterpreter(this).getValue(cell);
	}

	public XlsxTableHeaderMap getTableHeaderMap() {
		return tableHeaderMap;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mz.nhoz.xlsx.XlsxTableReader#getRecord(int)
	 */
	@Override
	public XlsxRecord getRecord(int recordIndex) {
		int rowIndex = recordIndex + 1;
		Row row = getRow(rowIndex);
		if (row == null) {
			return null;
		}

		return buildRecordFromRow(row);
	}// getRecord

	private XlsxRecord buildRecordFromRow(Row row) {
		CellValueInterpreter cellValueInterpreter = new CellValueInterpreter(this);
		Iterator<Cell> cellIterator = row.cellIterator();
		Iterator<XlsxTableHeader> headerIterator = tableHeaderMap.values().iterator();

		List<XlsxCell> cellList = new LinkedList<XlsxCell>();
		for (int i = 0; cellIterator.hasNext(); i++) {
			XlsxTableHeader header = headerIterator.next();
			Cell cell = cellIterator.next();
			Object cellValue = cellValueInterpreter.getValue(cell);
			XlsxCell xlsxCell = new XlsxCell(header, cellValue);
			cellList.add(xlsxCell);
		}

		return new XlsxRecord(cellList);
	}

	/**
	 * Recupera un registro a partir de un valor buscado.
	 * 
	 * @param headerName
	 *            - Nombre del encabezado por el cual comparar el valor de la
	 *            celda del registro..
	 * @param valueComparator
	 *            - Comparador de valores a usar (criterio de comparacion).
	 * @param expectedValue
	 *            - Valor esperado de la celda del registro buscado.
	 * @return Registro encontrado, null en caso de no existir.
	 */
	public XlsxRecord getRecord(String headerName, ValueComparator<?> valueComparator, Object expectedValue) {
		XlsxTableHeader header = this.tableHeaderMap.get(headerName);
		if (header == null) {
			return null;
		}

		Iterator<Row> rowIterator = getRowIterator();
		if (rowIterator.hasNext() == false) {
			return null;
		}

		rowIterator.next();
		while (rowIterator.hasNext()) {
			Row row = rowIterator.next();
			XlsxRecord record = buildRecordFromRow(row);

			Object recordValue = record.get(headerName);
			if (valueComparator.valuesAreEqual(expectedValue, recordValue)) {
				return record;
			}
		}

		return null;
	}// getRecord

	/**
	 * Recupera un registro a partir de un valor buscado.
	 * 
	 * @param header
	 *            - Encabezado por el cual buscar al registro.
	 * @param valueComparator
	 *            - Comparador de valores a usar (criterio de comparacion).
	 * @param expectedValue
	 *            - Valor esperado de la celda del registro buscado.
	 * @return Registro encontrado, null en caso de no existir.
	 */
	public XlsxRecord getRecord(XlsxTableHeader header, ValueComparator<?> valueComparator, Object expectedValue) {
		return getRecord(header.getName(), valueComparator, expectedValue);
	}

	private Iterator<Row> getRowIterator() {
		return sheet.rowIterator();
	}
}// XlsxTableManager
